/**
 * Copyright (C) 2010 altuure <altuure [AT] gmail [DOT] com> http://www.altuure.com/projects/yagdao
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.altuure.yagdao.common;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.altuure.yagdao.YOrder;
import com.altuure.yagdao.YOrder.Sort;

/**
 * Utility functions.
 * @author altuure
 **/
@SuppressWarnings("rawtypes")
public class Utils {
    private static final Log log = LogFactory.getLog(Utils.class);
    /**
     * dot seperator regex.
     */
    public static final String DOT_SEPERATOR_REGEX = "\\.";
    /**
     * dot.
     */
    public static final String DOT = ".";

    /**
     * Get the Generic definition from a class for given class with given index.
     * @param clz Implementing class
     * @param class1 class with generic definition
     * @param index generic index
     * @return null if not found
     */

    public static Type getGenericDefiniton(Class clz, Class class1, int index) {
        Type[] t = getGenericDefinitons(clz, class1);
        if (t != null && t.length > index)
            return t[index];
        return null;

    }

    /**
     * Get the Generic definitions from a class for given class .
     * @param clz Implementing class
     * @param class1 class with generic definition
     * @return null if not found
     */
    public static Type[] getGenericDefinitons(Class clz, Class class1) {
        Type[] t = null;
        while (clz != null) {

            t = getGenericDefinitonsThis(clz, class1);
            if (t != null)
                return t;
            Class[] interfaces = clz.getInterfaces();
            for (Class class2 : interfaces) {
                t = getGenericDefinitonsThis(class2, class1);
                if (t != null)
                    return t;
            }
            clz = clz.getSuperclass();
        }
        return t;
    }

    /**
     * Get the Generic definitions from a class for given class without looking
     * super classes.
     * @param classFrom Implementing class
     * @param interfaceClz class with generic definition
     * @return null if not found
     */
    @SuppressWarnings("unchecked")
    public static Type[] getGenericDefinitonsThis(Class classFrom, Class interfaceClz) {

        Type[] genericInterfaces = classFrom.getGenericInterfaces();
        for (Type type : genericInterfaces) {
            if (type instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) type;

                if (interfaceClz.isAssignableFrom((Class) pt.getRawType())) {
                    Type[] actualTypeArguments = pt.getActualTypeArguments();
                    return actualTypeArguments;
                }
            }

        }
        // check if it if available on generic super class
        Type genericSuperclass = classFrom.getGenericSuperclass();
        if (genericSuperclass instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) genericSuperclass;

            if (interfaceClz.equals(pt.getRawType())) {
                Type[] actualTypeArguments = pt.getActualTypeArguments();
                return actualTypeArguments;
            }
        }
        return null;

    }

    /**
     * Test if the text is empty or null.
     * @param text text to be tested
     * @return true if empty or null
     */
    public static boolean isEmpty(String text) {
        return text == null || text.trim().length() == 0;
    }

    /**
     * Test if the text is not empty.
     * @param text text to be tested
     * @return true if not empty
     */
    public static boolean isNotEmpty(String text) {
        return !isEmpty(text);
    }

    /**
     * Create instance of target class.
     * @param callback callback handler
     * @param superClass superclass
     * @param targetInterfaces targetinterfaces
     * @return object instance
     */
    public static Object createInstance(final InvocationHandler callback, Class superClass, Class... targetInterfaces) {
        Enhancer e = new Enhancer();

        e.setSuperclass(superClass);

        e.setCallback(new MethodInterceptor() {

            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                String name = method.getName();

                if (name.equals("toString"))
                    return " Proxy" + hashCode();
                if (log.isDebugEnabled()) {
                    log.debug("method:" + name);
                    log.debug("args = " + Arrays.asList(args));
                }
                if (!Modifier.isAbstract(method.getModifiers()))
                    return proxy.invokeSuper(obj, args);
                return callback.invoke(obj, method, args);
            }
        });
        if (targetInterfaces != null)
            e.setInterfaces(targetInterfaces);

        return e.create();
    }

    /**
     * upperCase only first string.
     * @param text whole input text
     * @return capitilized text
     */
    public static String capitilize(String text) {
        if (text == null)
            return null;
        if (text.length() == 0)
            return text;
        String result = text.substring(0, 1).toUpperCase(Locale.ENGLISH);
        if (text.length() > 1)
            result += text.substring(1);
        return result;
    }

    /**
     * split text with dot seperator.
     * @param text text to split
     * @return splitted text
     */
    public static String[] splitDot(String text) {
        return text.split(DOT_SEPERATOR_REGEX);
    }

    /**
     * Parse Order String.
     * @param orderBy .
     * @return parse statement from annotation
     */
    public static ArrayList<YOrder> parseOrderString(String orderBy) {
        ArrayList<YOrder> orders = new ArrayList<YOrder>();
        String[] split = orderBy.split(",");
        for (String string : split) {
            StringTokenizer st = new StringTokenizer(string);
            YOrder.Sort sort = Sort.ASC;
            String property = st.nextToken();
            if (st.hasMoreTokens()) {
                String orderString = st.nextToken().toUpperCase();
                sort = Sort.valueOf(orderString.trim());
            }
            orders.add(new YOrder(property.trim(), sort));

        }
        return orders;
    }

    /**
     * Scans all classes accessible from the context class loader which belong
     * to the given package and subpackages.
     * @param packageName The base package
     * @return The classes
     * @throws ClassNotFoundException underlying exception
     * @throws IOException   underlying exception
     */
    static Set<Class<?>> getClasses(String packageName) throws Exception {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        return getClasses(loader, packageName);
    }

    static Set<Class<?>> getClasses(ClassLoader loader, String packageName) throws IOException, ClassNotFoundException {
        Set<Class<?>> classes = new HashSet<Class<?>>();
        String path = packageName.replace('.', '/');
        Enumeration<URL> resources = loader.getResources(path);
        if (resources != null) {
            while (resources.hasMoreElements()) {
                String filePath = resources.nextElement().getFile();
                // WINDOWS HACK
                if (filePath.indexOf("%20") > 0)
                    filePath = filePath.replaceAll("%20", " ");
                if (filePath != null) {
                    if ((filePath.indexOf("!") > 0) & (filePath.indexOf(".jar") > 0)) {
                        String jarPath = filePath.substring(0, filePath.indexOf("!")).substring(
                                filePath.indexOf(":") + 1);
                        // WINDOWS HACK
                        if (jarPath.indexOf(":") >= 0)
                            jarPath = jarPath.substring(1);
                        classes.addAll(getFromJARFile(jarPath, path));
                    } else {
                        classes.addAll(getFromDirectory(new File(filePath), packageName));
                    }
                }
            }
        }
        return classes;
    }

    static Set<Class<?>> getFromDirectory(File directory, String packageName) throws ClassNotFoundException {
        Set<Class<?>> classes = new HashSet<Class<?>>();
        if (directory.exists()) {
            for (String file : directory.list()) {
                if (file.endsWith(".class")) {
                    String name = packageName + '.' + stripFilenameExtension(file);
                    Class<?> clazz = Class.forName(name);
                    classes.add(clazz);
                }
            }
        }
        return classes;
    }

    private static String stripFilenameExtension(String file) {
        return file.substring(0, file.length() - 6);
    }

    static Set<Class<?>> getFromJARFile(String jar, String packageName) throws FileNotFoundException, IOException,
            ClassNotFoundException {
        Set<Class<?>> classes = new HashSet<Class<?>>();
        JarInputStream jarFile = new JarInputStream(new FileInputStream(jar));
        JarEntry jarEntry;
        do {
            jarEntry = jarFile.getNextJarEntry();
            if (jarEntry != null) {
                String className = jarEntry.getName();
                if (className.endsWith(".class")) {
                    className = stripFilenameExtension(className);
                    if (className.startsWith(packageName))

                        try {
                            classes.add(Class.forName(className.replace('/', '.')));
                        } catch (Throwable t) {
                            log.warn("unable to load class:" + className, t);
                        }
                }
            }
        } while (jarEntry != null);
        return classes;
    }

    public static void main(String[] args) throws Exception {
        Set<Class<?>> classes = getClasses("org.springframework.orm.hibernate3.support");
        for (Class class1 : classes) {
            System.out.println(class1);

        }

        classes = getClasses("com.altuure.yagdao.dao");
        for (Class class1 : classes) {
            System.out.println(class1);
        }

        classes = getClasses("com.altuure.yagdao.common");
        for (Class class1 : classes) {
            System.out.println(class1);
        }

    }

}
